#include <stdio.h>
#include <assert.h>

#include "iconv/iconv.h"

#include "font.h"
#include "grphoal.h"
#include "canvas_impl.h"
#include "fontmgr_impl.h"

#include "agg/agg_conv_contour.h"
#include "agg/agg_conv_transform.h"


/*
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_scanline_u.h"
#include "agg_scanline_bin.h"
#include "agg_renderer_mclip.h"
#include "agg_renderer_primitives.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_pixfmt_rgb_packed.h"
#include "agg_conv_curve.h"

#include "agg_pixfmt_rgba.h"
*/

namespace agg
{

	template<class VertexSource> class faux_weight
	{
	public:
		faux_weight(VertexSource& vs) : 
		  m_mtx_zoom_in_y(agg::trans_affine_scaling(1, 100)),
			  m_mtx_zoom_out_y(agg::trans_affine_scaling(1, 1.0/100.0)),
			  m_source(&vs),
			  m_trans_zoom_in_y(*m_source, m_mtx_zoom_in_y),
			  m_contour(m_trans_zoom_in_y),
			  m_trans_zoom_out(m_contour, m_mtx_zoom_out_y) 
		  {
			  m_contour.auto_detect_orientation(false);
		  }

		  void weight(double v) { m_contour.width(v); }

		  void rewind(unsigned path_id=0) {m_trans_zoom_out.rewind(path_id); }
		  unsigned vertex(double* x, double* y) { return m_trans_zoom_out.vertex(x, y); }

	private:
		agg::trans_affine m_mtx_zoom_in_y;
		agg::trans_affine m_mtx_zoom_out_y;
		VertexSource* m_source;
		agg::conv_transform<VertexSource> m_trans_zoom_in_y;
		agg::conv_contour<agg::conv_transform<VertexSource> > m_contour;
		agg::conv_transform<agg::conv_contour<agg::conv_transform<VertexSource> > > m_trans_zoom_out;
	};

	extern bool g_agg_is_font_draw;

	class fontdrawflag
	{
	public:
		fontdrawflag() { agg::g_agg_is_font_draw = true; }
		~fontdrawflag() { agg::g_agg_is_font_draw = false; }
	};

};	// end of namespace agg

namespace visgrph
{

typedef agg::faux_weight<agg::conv_transform<agg::conv_curve<path_adaptor_type> > > faux_weight_type;

fontinfo::fontinfo(){}
fontinfo::~fontinfo(){}

const char* fontinfo::get_fontname(void)
{
	fontinfo_impl* impl = LIST_ENTRY(fontinfo_impl, stub, this);
	return impl->m_fontname;
}

fontstyle fontinfo::get_fontstyle(void)
{
	fontinfo_impl* impl = LIST_ENTRY(fontinfo_impl, stub, this);
	return impl->m_ftype;
}

fontinfo* fontinfo::set_default(font_charset type)
{
	fontmgr_impl* fontmgr = fontmgr_impl::inst();
	fontinfo_impl* impl = LIST_ENTRY(fontinfo_impl, stub, this);
	fontmgr->set_default(type, impl);
	return this;
}

font_charset fontinfo::get_default(void)
{
	fontmgr_impl* fontmgr = fontmgr_impl::inst();
	fontinfo_impl* impl = LIST_ENTRY(fontinfo_impl, stub, this);
	return fontmgr->check_font_default(impl);
}

// -------------------font --------------------------

font::font(char* fontname, unsigned int width,
	unsigned int height, fontstyle ftype,
	unsigned int faceid)
: impl(NULL)
{
	if (!width || !height || width > 0xFFFF || height > 0xFFFF)
		return;

	fontinfo_impl* fi = fontmgr_impl::inst()
		->find_font(fontname, faceid, ftype);
	if (NULL == fi) return;

	font_charset fchset = fi->get_charset();
	assert(fchset >= 0 && fchset < fcs_max);

	impl = new font_impl;
	if (NULL == impl) return;

	impl->fontsize = (width << 16) | height;

	// todo: decref & addref
	impl->finfo[fchset] = fi;
}

font::~font()
{
}

unsigned int font::get_fontsize(void) const
{
	if (!impl) return 0;
	return impl->fontsize;
}

unsigned int font::width(void) const
{
	if (!impl) return 0;
	return impl->fontsize >> 16;
}

unsigned int font::height(void) const
{
	if (!impl) return 0;
	return impl->fontsize & 0xFF;
}

fontinfo* font::get_fontinfo(font_charset fchset) const
{
	if (!impl) return NULL;
	if (fchset < 0 || fchset >= fcs_max)
		return NULL;

	fontinfo_impl* fi = impl->finfo[fchset];
	if (fi) return fi->get_fontinfo();
	return NULL;
}

void font::char_spacing(float pix)
{
	if (!impl) return;
	impl->char_spacing(pix);
}

bool font::line_spacing(float pix)
{
	if (!impl) return false;
	return impl->line_spacing(pix);
}

bool font::line_scale(float ratio)
{
	if (!impl) return false;
	return impl->line_scale(ratio);
}

fontinfo* register_font(const char* fntname,
	const char* filename, unsigned int faceid,
	font_charset type, fontstyle ftype)
{
	fontinfo_impl* impl = fontmgr_impl::inst()->add_font(fntname,
		filename, faceid, type, ftype);
	if (NULL == impl) return NULL;

	return impl->get_fontinfo();
}

static formatted_text_impl* init_formatted_text(font_impl* fimpl)
{
	formatted_text_impl* impl = new formatted_text_impl();
	if (NULL == impl) return NULL;
	if (fimpl) impl->init(fimpl);
	return impl;
}

formatted_text::formatted_text()
: impl(NULL)
{
}

formatted_text::~formatted_text()
{
	if (impl)
		delete impl;
	impl = NULL;
}

formatted_text::formatted_text(std::string content, bool bclone)
: impl(init_formatted_text(NULL))
{
	if (NULL == impl) return;
	impl->text(content, bclone);
}

formatted_text::formatted_text(const char* content, bool bclone)
: impl(init_formatted_text(NULL))
{
	if (NULL == impl) return;
	impl->text(content, bclone);
}

formatted_text::formatted_text(font& f, std::string content, bool bclone)
: impl(init_formatted_text(f._impl()))
{
	if (NULL == impl) return;
	impl->text(content, bclone);
}

formatted_text::formatted_text(font& f, const char* content, bool bclone)
: impl(init_formatted_text(f._impl()))
{
	if (NULL == impl) return;
	impl->text(content, bclone);
}

const char* formatted_text::text(const char* txt, bool bclone)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return NULL;
	}
	if (!impl->text(txt, bclone))
		return NULL;
	return impl->text();
}

bool formatted_text::setfont(const font& f)
{
	if (NULL == impl) return false;
	return impl->init(f._impl());
}

bool formatted_text::setfont(fontinfo* finfo, int fwidth, int fheight)
{
	if (NULL == impl || NULL == finfo) return false;
	if (fwidth <= 0 || fwidth > 0xFFFF
		|| fheight <= 0 || fheight > 0xFFFF)
		return false;

	unsigned int fontsize = (unsigned int)(fheight)
		| (unsigned int)(fwidth << 16);
	fontinfo_impl* fimpl = LIST_ENTRY(fontinfo_impl, stub, finfo);
	impl->fcache().init(fimpl, fontsize);
	return true;
}

formatted_text::formatted_text(const formatted_text& c)
{
	if (this != &c)
		impl = c.impl;
}

formatted_text& formatted_text::operator=(const formatted_text& c)
{
	if (this != &c)
		impl = c.impl;
	return *this;
}

bool formatted_text::wordwrap(void)
{
	if (NULL == impl) return false;
	return impl->wordwrap();
}

bool formatted_text::wordwrap(bool w)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	return impl->wordwrap(w);
}

int formatted_text::wrapping_width(void)
{
	if (NULL == impl) return 0;
	return impl->wrapping_width();
}

int formatted_text::wrapping_width(int w)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	return impl->wrapping_width(w);
}

int formatted_text::wrapping_height(void)
{
	if (NULL == impl) return 0;
	return impl->wrapping_height();
}

int formatted_text::wrapping_height(int h)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	return impl->wrapping_height(h);
}

bool formatted_text::wrapping_rect(int w, int h)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	impl->wrapping_rect(w, h);
	return true;
}

bool formatted_text::get_wrapping_rect(int& w, int& h)
{
	if (NULL == impl) return false;
	impl->wrapping_rect(w, h);
	return true;
}

bool formatted_text::left(void)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	return impl->set_alignment(dt_left);
}

bool formatted_text::center(void)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	return impl->set_alignment(dt_center);
}

bool formatted_text::middle(void)
{
	if (NULL == impl)
	{
		impl = init_formatted_text(NULL);
		if (NULL == impl) return false;
	}
	return impl->set_alignment(dt_middle);
}

std::string to_utf8(const char* str, const char* ot)
{
	std::string ret;
	if (NULL == str || NULL == ot)
		return ret;

	iconv_t cd = iconv_open("UTF-8", ot);
	if (NULL == cd) return ret;

	const int bufsz = 256;
	char buffer[bufsz];
	const char* inbuf = str;
	size_t inleft = strlen(inbuf);

	while (inleft)
	{
		size_t outleft = bufsz - 1;
		char *outbuf = buffer;
		iconv(cd, &inbuf, &inleft, &outbuf, &outleft);
		*outbuf = 0;
		ret.append(buffer);
	}

	iconv_close(cd);
	return ret;
}

std::string to_utf8(const std::string& str, const char* ot)
{
	return std::string("");
}

// implement the functions of font in canvas
static const char* utf8_getchar(const char* str, unsigned int& c)
{
	if (!str)
	{
		c = 0;
		return NULL;
	}

	const char* ret = str;
	char tmp = *ret++;
	char data = tmp;

	// calculate how many 1's
	int count = 0;
	for (; tmp & 0x80; tmp <<= 1, ++count);
	c = ((unsigned int)data) & ((1 << (8 - count - 1)) - 1);
	
	// handle the continuous count bytes except for the first one
	for (; count > 1; --count)
	{
		if (!*ret)
		{
			// error
			c = 0;
			return str;
		}

		data = *ret++;
		data &= 0x3F;	// 00111111
		c = (c << 6) | data;
	}
	return ret;
}

enum chtype {
	chtype_unknown = -1,
	chtype_eol,
	chtype_nothing,
	chtype_normal,
	chtype_tab,
	chtype_saperator,
	chtype_eastern_seperator
};

#define is_saperator(c)	\
	((c) == chtype_saperator || (c) == chtype_tab)

static chtype parse_char(unsigned int& out)
{
	unsigned int in = out;
	chtype ret = chtype_unknown;

	switch (in)
	{
	case '\n': ret = chtype_eol; break;
	case '\r': ret = chtype_nothing; break;
	case ' ': ret = chtype_saperator; break; 
	case '\t': out = L' '; ret = chtype_tab; break;
	case 0xFF0C: ret = chtype_saperator; break;				// 全角逗号
	case 0x3001: ret = chtype_saperator; break;				// 全角顿号
	case 0x3002: ret = chtype_saperator; break;				// 全角句号
	default: ret = chtype_normal; break;
	}
	return ret;
}

static bool is_eastern_saperator(uint ch)
{
	// 全角逗号, 全角顿号, 全角句号 are regarded as
	// eastern style saperator
	const uint seps[] = { 0xFF0C, 0x3001, 0x3002, 0 };
	for (const uint* p = seps; *p; ++p)
		if (ch == *p) return true;
	return false;
}

static void init_embedded_adaptors(path_adaptor_type& pa,
	const agg::glyph_cache* gl, double x, double y, double scale = 1.0)
{
	if(gl && gl->data_type == agg::glyph_data_outline)
		pa.init((agg::int8u*)gl->data, gl->data_size, x, y, scale);
}

static double calc_advance_x(double x, double adv_x, double charspacing, int chartype, formatted_text_impl* fti)
{
	if (chtype_tab == chartype && fti->testflags(dt_expend_tabs | dt_left)
		== (dt_expend_tabs | dt_left))
	{
		double advance_x = adv_x * fti->get_drawtext_tab_length();
		unsigned int tmp = (unsigned int)((x + charspacing) / advance_x);
		return double(advance_x * (tmp + 1));
	}
	else return x + adv_x + charspacing;
}

void canvas_impl::drawtext(float _x, float _y, formatted_text_impl* fti, const char* str, color c, int sz)
{
	if (!sz) return;

	assert(NULL != fti);
	if (NULL == str || *str == '\0') return;

	agg::fontdrawflag drawfont;

	double x = _x;
	double y = _y;

	if (!str || !*str || !c.alpha())
		return;

	agg::rgba8 color(c.red(), c.green(), c.blue(), c.alpha());
	m_ren_solid_lcd.color(color);

	double start_x = double(x) * f_subpixel_scale;
	double charspacing = fti->char_spacing() * f_subpixel_scale * f_scale_x;
	x = 0;

	path_adaptor_type path_adaptor;
	agg::trans_affine mtx;
	agg::rasterizer_scanline_aa<> ras;
	agg::conv_curve<path_adaptor_type> curves(path_adaptor);
	agg::conv_transform<agg::conv_curve<path_adaptor_type> > trans(curves, mtx);

	fontmgr_impl* fman = fontmgr_impl::inst();
	y = ceil(y + fman->descender() + fti->line_spacing(fman->height()));

	for (; *str && sz; )
	{
		unsigned int ch;
		const char* next = utf8_getchar(str, ch);
		if (next == str) break;

		sz -= next - str;
		if (sz < 0 && sz > -100) sz = 0;
		str = next;

		chtype chartype = parse_char(ch);
		if (chtype_nothing == chartype) continue;
		else if (chtype_eol == chartype)
		{
			x = 0;
			y += floor(fti->line_spacing(fman->height()));
			continue;
		}

		const agg::glyph_cache* glyph = fman->glyph(fti->fcache(), ch);
		if (NULL == glyph)
		{
			glyph = fman->glyph(fti->fcache(), 0x25A1);
			if (NULL == glyph) continue;
		}

		fman->add_kerning(x, y);

		if (glyph->data_type != agg::glyph_data_outline)
			break;

		init_embedded_adaptors(path_adaptor, glyph, 0, 0);

		ras.reset();
		mtx.reset();
		mtx *= agg::trans_affine_scaling(1.0 / f_scale_x, 1);
		mtx *= agg::trans_affine_translation(start_x + x / f_scale_x, y);

		ras.add_path(trans);
		agg::scanline_u8 sl;
		agg::render_scanlines(ras, sl, m_ren_solid_lcd);

		// increment pen position
		x = calc_advance_x(x, glyph->advance_x, charspacing, chartype, fti);
		y += glyph->advance_y;
    }
}

const char* formatted_text_impl::truncate(const char* str, int sz, float& _width)
{
	const char* next = str;
	if (!str || !*str || !sz)
	{
		_width = 0.0;
		return next;
	}

	double x = 0, y = 0;
	double charspacing = char_spacing() * f_subpixel_scale * f_scale_x;
	double w = double(_width) * double(f_scale_x * f_subpixel_scale);

	const char *orig_str = str;
	const char *cur_word_start = str;
	chtype curr_chtype, prev_chtype = chtype_unknown;

	enum {first = 0, word = 1, space = 2} prop = first;
	const agg::glyph_cache* glyph = NULL;

	fontmgr_impl* fman = fontmgr_impl::inst();

	// need lock
	fman->lock();

	// make use of current font
	fman->activate_fcache_adaptor(fcache());

	for (; *str && sz; prev_chtype = curr_chtype, str = next)
	{
		unsigned int ch;
		next = utf8_getchar(str, ch);
		if (next == str) break;

		curr_chtype = parse_char(ch);
		if (chtype_nothing == curr_chtype) continue;
		else if (chtype_eol == curr_chtype) break;

		// udate the remaining size
		sz -= next - str;
		if (sz < 0 && sz > -100) sz = 0;

		glyph = fman->glyph(fcache(), ch);
		if (NULL == glyph)
		{
			glyph = fman->glyph(fcache(), 0x25A1);
			if (NULL == glyph) continue;
		}

		fman->add_kerning(x, y);

		// increment pen position
		double next_x = calc_advance_x(x, glyph->advance_x, charspacing, curr_chtype, this);

		if (next_x >= w)
		{
			fman->unlock();
			return (prop == first || prop == space) ? str : cur_word_start; // user the result from 1)
		}

		// for the second word
		if (str != orig_str)
		{
			if (is_saperator(prev_chtype) && !is_saperator(curr_chtype))
			{
				// 1) this is a start of a word. We need to save the width before the
				// start of this word, since we may come to end in middle way of parsing
				// a word. In this case we need to return to the start of the word
				prop = word, cur_word_start = str;
			}
			else if (!is_saperator(prev_chtype) && is_saperator(curr_chtype))
			{
				_width = float((is_eastern_saperator(ch) ? next_x : x)
					/ double(f_scale_x * f_subpixel_scale));
				prop = space, cur_word_start = str;
			}
		}
		x = next_x;
	}
	_width = float(x / double(f_scale_x * f_subpixel_scale));
	fman->unlock();
	return next;
}

bool canvas_impl::check_rect(rect& r)
{
	if (r.width <= 0 || r.height <= 0)
		return false;
	if ((unsigned int)r.left >= m_rbuf.width()
		|| (unsigned int)r.top >= m_rbuf.height())
		return false;
	if (r.left + r.width < 0 || r.top + r.height < 0)
		return false;
	return true;
}

static uint firstline(const char *str)
{
	if (NULL == str) return 0;
	const char *start = str;
	for (; *str && *str != '\n'; str++);
	return str - start;
}

#define _INT_MIN		(-2147483647 - 1)
#define _INT_MAX		2147483647
#define _UINT_MAX	0xffffffff

static double line_spacing(formatted_text_impl* fti)
{
	assert(NULL != fti);
	return floor(fti->line_spacing(fontmgr_impl::inst()->height()));
}

bool canvas_impl::get_actual_row_range(float y,
	formatted_text_impl* fti, uint lines, uint& start, uint& end)
{
	assert(NULL != fti);

	fontmgr_impl* fman = fontmgr_impl::inst();
	double dy = line_spacing(fti);
	double yend = (lines) ? float(lines) * dy : 0.;
	int ymin = m_ren_base_lcd.bounding_ymin();
	int ymax = m_ren_base_lcd.bounding_ymax();

	start = 0;
	end = _UINT_MAX;
	if (y > ymax) return false;
	if (lines && yend < ymin) return false;

	// calculate the start
	if (ymin > y) start = (uint)((float(ymin) - start) / dy);

	// calculate the end
	if (lines && yend < ymax) end = lines;
	else
	{
		// end = (uint)ceil((ymax - y) / dy);
		double val = ((ymax - y) / dy);
		uint f = (uint)val;
		end = (double(f) < val) ? f + 1 : f;
	}
	return true;
}

// fman must be locked and initialized
bool canvas_impl::draw_formattedtext(float x, float y, formatted_text_impl* fti,
	color c, uint line_start, uint line_end)
{
	line_iter li;

	// execute pending formatting if necessary
	fti->execute_text_formatting();

	extend_format* fmt = fti->format();
	assert(NULL != fmt);

	float dy = float(line_spacing(fti));

	// calcute y Position
	if (fti->testflags(dt_middle))
	{
		int height = fti->wrapping_height();
		if (height <= 0) return true;
		y += (float(height) - float(fmt->line_count() * dy)) / 2;
	}
	y += dy * line_start;
	int wrapping_width = fti->wrapping_width();

	// get the first line
	if (!fmt->line(line_start, li))
		return false;

	for (; li.line_number < line_end; y += dy)
	{
		// draw line
		fmt_paragraph *p = fmt->paragraph(li.paragraph);
		if (!p) return false;

		// calcuate the start x
		float calc_x = x;
		if (wrapping_width > 0)
		{
			if (fti->testflags(dt_center))
				calc_x += (float(wrapping_width) - li.line->width) / 2;
		// todo:
		}
		
		drawtext(calc_x, y, fti, p->text() + li.line->start, c, li.line->length);
		if (!fmt->next_line(li)) return false;
	}
	return true;
}

bool canvas_impl::set_textclip(int x, int y, formatted_text_impl* fti,
		agg::rect_i& bounds, agg::pod_bvector<agg::rect_i, 4>& dup)
{
	int width = fti->wrapping_width();
	int height = fti->wrapping_height();
	bool need_clip = (!fti->testflags(dt_noclip) && width > 0 && height > 0);
	if (!need_clip) return true;
	
	agg::rect_i r;
	r.x1 = x * f_subpixel_scale;
	r.y1 = y;
	r.x2 = (x + width - 1) * f_subpixel_scale;
	r.y2 = y + height - 1;
	
	m_ren_base_lcd.duplicate_clip_box(dup);
	m_ren_base_lcd.clip(r, bounds);
	return true;
}

bool canvas_impl::clear_textclip(formatted_text_impl* fti,
		const agg::rect_i& bounds, agg::pod_bvector<agg::rect_i, 4>& dup)
{
	if (fti->testflags(dt_noclip) || fti->wrapping_width() <= 0
		|| fti->wrapping_height() <= 0)
		return true;

	m_ren_base_lcd.assign_clip_box(dup, bounds);
	return true;
}

bool canvas_impl::textout(float x, float y, formatted_text_impl* fti, color c, uint sz)
{
	if (NULL == fti) return false;

	const char *str = fti->text();
	extend_format* fmt = fti->format();

	if (!str)
	{
		if (!fmt) return false;
		if (!fmt->line_count()) return true;
	}

	// prepare for drawing text
	// need lock and make use of current font
	fontmgr_impl* fman = fontmgr_impl::inst();
	fman->lock();
	fman->activate_fcache_adaptor(fti->fcache());

	// prepare clip box
	agg::rect_i bounds;
	agg::pod_bvector<agg::rect_i, 4> tmp;
	if (!set_textclip(int(x), int(y), fti, bounds, tmp))
	{
		fman->unlock();
		return true;
	}

	uint line_start, line_end;
	if (!get_actual_row_range(y, fti, (str) ? 0 : fmt->line_count(),
		line_start, line_end))
	{
		fman->unlock();
		clear_textclip(fti, bounds, tmp);
		return false;
	}

	if (!str)
	{		
		if (draw_formattedtext(x, y, fti, c, line_start, line_end))
		{
			fman->unlock();
			clear_textclip(fti, bounds, tmp);
			return false;
		}
	}
	else drawtext(x, y, fti, str, c, sz);

	fman->unlock();
	clear_textclip(fti, bounds, tmp);
	return true;
}

}; // namespace visgrph
/* EOF */
